package fun.wlfj.flashlight;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
import com.github.javaparser.ast.expr.AssignExpr;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.IntegerLiteralExpr;
import com.github.javaparser.ast.expr.ObjectCreationExpr;
import com.github.javaparser.ast.stmt.BlockStmt;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;

import junit.framework.Test;

public class FlashLight {
	
	public static final String FILE_PATH = "resources/TestHello.java";

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		FlashLight fl = new FlashLight();
		fl.job();
	}
	
	public void job() throws IOException {
		CompilationUnit cu = StaticJavaParser.parse(new File(FILE_PATH));
		TestMethodFinder maker = new TestMethodFinder();
		StructType irTable = new StructType("Test");
		maker.visit(cu, irTable);
		
		// 现在我们要想办法打印这个表
		List<String> list = getIRTableStringList(irTable);
		list.forEach(s -> System.out.println(s));
		
		// 下面我们要根据这个表创建变量了。第一层表是不用创建的，关键是第二层表的内容。
		
	}
	
	public List<String> getIRTableStringList(StructType irTable) {
		List<String> line = new ArrayList<>();
		line.add("*** Table Name: " + irTable.getTypeName());
		for(String key : irTable.map.keySet()) {
			// 这里要看value的类型了

			line.add("> " + key + " ~ " + irTable.get(key).getTypeName());
			if(irTable.get(key) instanceof SingleType) {
			} else {
				List<String> subList = getIRTableStringList((StructType)irTable.get(key));
				for(String s : subList) {
					line.add("> " + s);
				}
			}
		}
		return line;
	}

}

class TestMethodFinder extends VoidVisitorAdapter<StructType> {
	
	MethodResolver resolver = new MethodResolver();
	
	@Override
	public void visit(MethodDeclaration md, StructType v) {
		super.visit(md, v);
		
		// 现在我们要检查其前一个节点的信息是否包含@Test
		Optional<AnnotationExpr> testAnnotation = md.getAnnotationByClass((Class<? extends Annotation>) Test.class);
		if(testAnnotation.isPresent()) {
			Optional<BlockStmt> bs = md.getBody();
			if(bs.isPresent()) {
				resolver.visit(bs.get(), v);
			}
		}
		
	}
}

class MethodResolver extends VoidVisitorAdapter<StructType> {
	

	
	
	@Override
	public void visit(AssignExpr ae, StructType irTable) {
		// a = b
		super.visit(ae, irTable);
		
		// 现在我们要查找其对应的IRTable，然后将变量创建
		List<Node> list = ae.getChildNodes();
		assert(list.size() == 2);
		
		Node assignLeft = list.get(0);
		Node assignRight = list.get(1);
		
		List<Node> assignLeftList = assignLeft.getChildNodes().get(0).getChildNodes();
		// assignLeftList.forEach(n -> System.out.println(n));
		
		Type curType = (Type)irTable;
		// 找到倒数第二个
		int i;
		for(i = 0; i < assignLeftList.size() - 1; i ++) {
			Node curNode = assignLeftList.get(i);
			curType = irTable.getOrSet(curNode.toString(), new StructType(curNode.toString()));
		}
		
		// 将信息加入，这个时候我们就要推断其类型了
		System.out.println(curType.getTypeName() + "??");
		StructType structType = (StructType) curType;
		try {
			structType.set(assignLeftList.get(i).toString(), getType(assignRight));
		} catch (TypeNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			System.out.println("已跳过该语句...");
		}
	}
	
	@Override
	public void visit(ObjectCreationExpr oce, StructType irTable) {
		// new
		// 我们希望知道变量是谁，构造器里的类型是什么
		super.visit(oce, irTable);
		Optional<Node> partialExpr = oce.getParentNode();
		if(partialExpr.isPresent()) {
			Node expr = partialExpr.get();
			// 现在我们要从里面解析出来谁？什么类型？
			Node var = expr.getChildNodes().get(1);
			
			// 现在开始变量的创建
			// TODO 现在我们只支持空构造器，未来我们要支持带参数的设计
			Type type = new StructType(oce.getTypeAsString());
//			System.out.println("???" + oce.getArguments().stream().map(n -> n.getClass()).toArray());
			List<Expression> args =  oce.getArguments();
			args.forEach(e -> System.out.println("xx" + e.getClass()));
			irTable.set(var.toString(), type);
		}
	}
	
	public Type getType(Node n) throws TypeNotSupportedException {
		// TODO：现在我们只支持最简单的基础类型推断，其他的之后再说吧。
		Class<?> nodeType = n.getClass();
		if(nodeType == IntegerLiteralExpr.class) {
			return new SingleType("int");
		} else {
			throw new TypeNotSupportedException(n);
		}
		
	}
}

interface Type {
	public void setTypeName(String repr);
	public String getTypeName();
}

class SingleType implements Type {
	
	String repr;
	
	public SingleType(String repr) {
		this.repr = repr;
	}

	@Override
	public void setTypeName(String repr) {
		this.repr = repr;
	}

	@Override
	public String getTypeName() {
		// TODO Auto-generated method stub
		return repr;
	}
	
}

class StructType extends TableIR implements Type {
	
	public String repr;
	
	public StructType(String repr) {
		this.repr = repr;
	}

	@Override
	public void setTypeName(String repr) {
		this.repr = repr;
	}

	@Override
	public String getTypeName() {
		// TODO Auto-generated method stub
		return repr;
	}
}

interface IR {
	/**
	 * 什么是IR？在这里IR本质上是一种中间标识表，以k-v结构声明每种对象所指代的类型。
	 * 我们还需要实现其对应的CRUD方法。
	 */
	public Type get(String attrName);
	public Type getOrSet(String attrName, Type alterType);
	public void set(String attrName, Type alterType);
}

class TableIR implements IR {
	
	public Map<String, Type> map = new HashMap<String, Type>();

	@Override
	public Type get(String attrName) {
		return map.get(attrName);
	}

	@Override
	public Type getOrSet(String attrName, Type alterType) {
		if(map.containsKey(attrName)) {
			return map.get(attrName);
		} else {
			set(attrName, alterType);
			return alterType;
		}
	}

	@Override
	public void set(String attrName, Type alterType) {
		map.put(attrName, alterType);
	}
	
}

class TypeNotSupportedException extends Exception{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	Node unsupportedNode;
	
	public TypeNotSupportedException(Node n) {
		super("不支持的类型推断：" + n.getClass().toString()); // 这里我们要构建Message
		this.unsupportedNode = n;
	}
	
}